#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <functional>
#include <unordered_map>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

// uint16_t:2字节，uint32_t:4字节

// c++内置的函数对象,就可以把一个返回值是string，参数是const string&的函数传递给udpserver,可以当做函数指针
// using func_t=std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

extern Log log;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

class UdpServer
{
public:
    // ip用户使用的是ipv4点分10进制的形式，所以ip地址类型传进来是一个字符串
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip) : sockfd_(0), port_(port), ip_(ip), isrunning_(false)
    {
    }
    void Init()
    {
        // 1.创建 udp socket
        //2.udp的socket是全双工的，是允许被同时读写的
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET也可写成PF_INET，都是ipv4
        if (sockfd_ < 0)
        {
            log(Fatal, "socket create error,sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        log(Info, "socket create success,sockfd: %d", sockfd_); // 根据文件部分知识，这样创建成功后的fd应该是3

        // 2.bind socket
        struct sockaddr_in local;     // 不用sockaddr，这个是底层封装用的结构
        bzero(&local, sizeof(local)); // bzero将指定类型的大小清0，第二个参数就是这个指定类型的大小

        // s表示sock，in表示inet
        local.sin_family = AF_INET;    // 指定域
        local.sin_port = htons(port_); // 因为该端口号是要给对方发送的，所以需要保证我的端口号是网络字节序列,如果网络字节序是大端，当前主机是大端，htons什么也不做，如果主机是小端，就先转大端再将字段填到sockaddr_in中

        // 1.sin_addr转定义会发现是一个inaddr的结构体，再转定义结构体内部是一个in_addr_t类型的成员s_addr，in_addr_t就是uint32_t,所以要填充这个字段的前提要先将string->uint_32
        // 2.ip地址也要发给对方，所以ip也必须符合网络字节序，所以uint_32也要是网络字节序的
        // 3.inet_addr能将字符串类型ip地址转换成in_addr_t即uint32_t，并且转换成的uint32_t类型的ip地址是符合网络字节序的
        local.sin_addr.s_addr = inet_addr(ip_.c_str());
        // local.sin_addr.s_addr=htonl(INADDR_ANY);   //INADDR_ANY表示可以绑定任意ip地址，其实就是0，加不加htonl都行

        // 由于local的前16位字段我们设置的为AF_INET，所以强转为sockaddr后bind内部就会对前16位地址类型做判断，如果是AF_INET，就使用网络通信的一套设置
        if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            log(Fatal, "bind errno,errno: %d,err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success,errno: %d,err string: %s", errno, strerror(errno));
    }

    void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport)
    {
        auto iter = online_user_.find(clientip);
        if (iter == online_user_.end())
        {
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;
        }
    }

    //转发消息
    void Broadcast(const std::string &info, const std::string clientip, uint16_t clientport)
    {
        for (const auto &user : online_user_)
        {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]# ";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr *)(&user.second), len);
        }
    }

    // 服务器需要一直运行，就像刷抖音能随时随地的刷
    // void Run(func_t fun)   //这里参数目的将代码分层，将数据处理与接收解耦
    void Run()
    {
        isrunning_ = true;
        char inbuffer[size];
        while (isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len); // sizeof(buffer-1)是因为读的是字符串
            if (n < 0)
            {
                log(Warning, "recvfrom error,errno: %d,err string: %s", errno, strerror(errno));
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);
            CheckUser(client, clientip, clientport);

            std::string info = inbuffer;
            Broadcast(info,clientip, clientport);
        }
    }

    // 其实调用析构说明服务器关了，服务器关了一般进程都没了，整体资源都回收了，所以可以不用close，并且服务器一般不会关
    ~UdpServer()
    {
        if (sockfd_ > 0)
            close(sockfd_);
    }

private:
    int sockfd_;     // 网络文件描述符
    std::string ip_; // 云服务器上一般设为0，表示任意地址
    uint16_t port_;  // 表明服务器进程的端口号
    bool isrunning_;
    std::unordered_map<std::string, struct sockaddr_in> online_user_; // key是ip地址，val是网络版本的客户端的套接字信息
};